//#define __COMPILE_DEBUG__
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>

#include "zepto.h"
#include "list.h"
#include "internal.h"

static size_t mbslen(const char *str)
{
  int len = 0;
  int k;
  
  while(1) {
    k = mblen(str, 6);
    if(k <= 0) {
      break;
    }
    str += k;
    len++;
  }
  return len;
}

static struct zepto *get_atom(struct list_head *tokens)
{
  struct token *token;
  struct zepto *atom;
  int k, l;
  bool new;


  DEBUG("A1");
  token = list_first_entry(tokens, struct token);
  DEBUG("A2");
  
  switch(token->type) {
    case TOK_SYMBOL:
      DEBUG("SY");
      atom = sym_intern(token->data.symbol, &new);
      if(!new) {
        free(token->data.symbol);
      }
      DEBUG("SY2");
      break;
    case TOK_STRING:
      DEBUG("ST");
      atom = zepto_alloc();
      atom->type = ZEPTO_STRING;
      DEBUG("string: %s", token->data.symbol);
      l = mbslen(token->data.symbol);
      DEBUG("mbslen: %d", l);
      atom->data.string.length = l;
      atom->data.string.data = zepto_alloc_general(sizeof(wchar_t) * (l + 1));
      k = mbstowcs(atom->data.string.data, token->data.symbol, l);
      DEBUG("lengths: %d %d", l, k);
      ASSERT(l == k);
      atom->data.string.data[l] = L'\0';
      break;
    case TOK_INTEGER:
      DEBUG("IN %ld", token->data.integer);
      atom = zepto_alloc();
      atom->type = ZEPTO_INTEGER;
      atom->data.integer = token->data.integer;
      break;
    case TOK_TRUE:
      DEBUG("TRUE");
      atom = TRUE;
      break;
    case TOK_FALSE:
      DEBUG("FALSE");
      atom = FALSE;
      break;
    default:
      DEBUG("SHIT");
      zepto_errno = ZEPTO_EXPR_UNEXPECTED_TOKEN;
      goto ga_cleanup;
  }

  list_del_first(tokens);
  free(token);
  return atom;

  ga_cleanup:
  free(atom);

  ERROR("Cannot read atom.\n");
  
  return NULL;
}

#define CHECK_NOT_EMPTY(a)                      \
  {                                             \
    if(list_empty(a)) {                         \
      ERROR("Unexpected end of tokens");        \
    }                                           \
  }
  
static struct zepto *get_sexpr(struct list_head *tokens)
{
  struct zepto *res;
  struct zepto **target;
  struct token *token;
  bool dot_allowed;
  
  CHECK_NOT_EMPTY(tokens);

  token = list_first_entry(tokens, struct token);
  
  switch(token->type) {
  case TOK_SYMBOL:
  case TOK_STRING:
  case TOK_TRUE:
  case TOK_FALSE:
  case TOK_INTEGER:
    res = get_atom(tokens);
    break;
  case TOK_OP_B:
    target = &res;
    dot_allowed = false;
    list_del_first(tokens);
    while(1) {
      CHECK_NOT_EMPTY(tokens);
      token = list_first_entry(tokens, struct token);
      if(token->type == TOK_CL_B) {
        *target = NIL;
        break;
      } else if(token->type == TOK_DOT && dot_allowed) {
        // (a b . c) notation
        list_del_first(tokens);
        *target = get_sexpr(tokens);
        CHECK_NOT_EMPTY(tokens);
        token = list_first_entry(tokens, struct token);
        if(token->type != TOK_CL_B) {
          ERROR("Illegal use of '.'");
        }
        break;
      }
      *target = zepto_alloc();
      (*target)->type = ZEPTO_PAIR;
      CAR(*target) = get_sexpr(tokens);
      dot_allowed = true;
      target = &CDR(*target);
    }
    list_del_first(tokens);
    break;
  case TOK_QUOTE:
    list_del_first(tokens);
      
    res = MK_CONS(CQUOTE, get_sexpr(tokens));
    break;
  default:
    ERROR("Unexpected token.\n");
  }
  
  return res;
}

struct zepto *_stream_read_expr(stream_t *stream)
{
  struct list_head list;
  int res;

  struct zepto *r;

  res = _stream_tokenize(&list, stream);

  if(res == EOF) {
    return NULL;
  }

  DEBUG("Tokenized");
  
  r = get_sexpr(&list);

  return r;
}
